﻿#include "businessnetwork.h"

#include "../Network/network.h"
#include <hv/TcpClient.h>

#include "AppConfig/appconfig.h"
#include "CipherUtils/cipherutils.h"

#include <QJsonObject>

#include "protocol/protocolBuilder.h"

#include "ThreadPool/threadpool.h"
#include "businessmsghandler.h"

#include <SyncManager/syncmanager.h>
#include <Debug/DEBUG.hpp>
#include "businessviewmodel.h"
#include <protocol/protocolConverter.h>
#include <algorithm>

BusinessNetWork::BusinessNetWork()
{
    protoOpt = std::make_shared<ProtocolOption>();
}

bool BusinessNetWork::connectTargetServer(const std::string &ip)
{
    Network &net = Network::getInstance();
    // connect to the target server, port is fixed
    int connfd = net.createsocket(23333, ip.c_str());
    // ret < 0 == connect failed
    if (connfd < 0)
    {
        return false;
    }
    return true;
}

/**
 * @brief BusinessNetWork::sendMessageToServer
 * @param in
 * @param out
 *
 * build protocol and encrypt the message according to cryptographic rules and send them
 */

void BusinessNetWork::sendMessageToServer(int type,
                                          int subtype,
                                          int64_t id1,
                                          int64_t id2,
                                          int64_t id3,
                                          unsigned char *data,
                                          int length,
                                          bool initClient)
{
    Network &net = Network::getInstance();
    if (!net.isConnected()) {
        DEBUG_INFO("连接已断开，取消发送信息");
        return;
    }
    DEBUG_INFO("server send message : " + std::to_string(type) + "|" + std::to_string(subtype));
    ProtocolBuilder builder;
    builder.setProtocolOption(protoOpt);

    builder.setHeaderField(0, (int64_t)type);
    builder.setHeaderField(1, (int64_t)subtype);
    builder.setHeaderField(2, id1);
    builder.setHeaderField(3, id2);
    builder.setHeaderField(4, id3);

    sendLock.lock();


    // if used to init the client
    if (initClient)
    {
        auto message = builder.buildWithHeaderAndData(data, length);
        net.send(message->data(), message->size());
        return;
    }

    AppConfig &config = AppConfig::getInstance();
    // get the mode of the server transmission
    int mode = std::stoi(AppConfig::getInstance().runtimeValue("TransmissionMode"));
    switch (mode)
    {
    case 1:
    {
        // if the message don't need to be full encrypted
        auto message = builder.buildWithHeaderAndData(data, length);
        net.send(message->data(), message->size());
        break;
    }
    case 2:
    {
        // if the message need to encrypt the data part

        const std::string &AESKey = config.runtimeValue("RemoteAESKey");
        const std::string &AESIV = config.runtimeValue("RemoteAESIV");

        CipherUtils &utils = CipherUtils::getInstance();
        auto encryptedMessage = utils.AES_Encrypt((char *)data, length,
                                                  (unsigned char *)AESKey.data(),
                                                  (unsigned char *)AESIV.data());
        auto message = builder.buildWithHeaderAndData(encryptedMessage.first.get(), encryptedMessage.second);
        net.send(message->data(), message->size());
        break;
    }
    default:
        qWarning() << "sendMessageToServer error, wrong encrypt mode";
        break;
    }
    return;
}

uint64_t BusinessNetWork::requestUniqueID()
{
    SyncManager &syncManager = SyncManager::getInstance();
    SyncPoint point = syncManager.generateUniqueSyncPoint();

    BusinessNetWork &net = BusinessNetWork::getInstance();

    net.sendMessageToServer(4, 1, 0, 0, point, nullptr, 0);
    uint64_t id = syncManager.waitForSyncPoint(point);
    return id;
}

bool BusinessNetWork::loginTargetServer(const std::string &account, const std::string &shaPassword)
{
    AppConfig &config = AppConfig::getInstance();
    // set login message
    nlohmann::json messageData;
    messageData["account"] = account;
    messageData["password"] = shaPassword;
    messageData["ECC_public"] = config.globalValue("ECC_public");
    DEBUG_INFO(messageData["ECC_public"]);
    // get the str
    std::string messageToSend = messageData.dump();

    SyncManager &sync = SyncManager::getInstance();
    SyncPoint point = sync.generateUniqueSyncPoint();
    // send the message according to the mode

    sendMessageToServer(7, 1, 0, 0, point,
                        (unsigned char *)messageToSend.data(),
                        messageToSend.size(), true);
    return sync.waitForSyncPoint(point);
}

BusinessNetWork &BusinessNetWork::getInstance()
{
    static BusinessNetWork instance;
    return instance;
}


void BusinessNetWork::newNetConnection(const hv::SocketChannelPtr &channel)
{
    if (!channelBuffer.count(channel)) {
        // if not exist
        channelBuffer.emplace(channel, std::make_shared<Buffer>());
        return ;
    }
    auto& one = channelBuffer[channel];
    one->addedSize = 0;
    one->totalSize = 0;
}

void BusinessNetWork::deleteNetConnection(const hv::SocketChannelPtr &channel)
{
    channelBuffer.erase(channel);
}

std::shared_ptr<Buffer> BusinessNetWork::getChannelBuffer(const hv::SocketChannelPtr &channel)
{
    return channelBuffer[channel];
}

void BusinessNetWork::initNet()
{
    // set reconnect value

    reconn_setting_t reconnectSetting;
    reconn_setting_init(&reconnectSetting);
    // 1s, 2s, 4s, 8s, 10s
    reconnectSetting.min_delay = 1000;
    reconnectSetting.max_delay = 10000;
    reconnectSetting.delay_policy = 2;
    reconnectSetting.max_retry_cnt = 5;

    Network &net = Network::getInstance();

    net.setReconnect(&reconnectSetting);

    // set the protocol setting;

    protoOpt->append(1, GINT, "type of message");
    protoOpt->append(1, GINT, "sub type of message");
    protoOpt->append(8, GINT, "id of A");
    protoOpt->append(8, GINT, "id of B");
    protoOpt->append(8, GINT, "id of request");
    protoOpt->append(2, GSIZE, "size of total protocol");

    // set tcpclient callback
    // when connect changed
    AppConfig::getInstance().setRuntimeValue("ConnectionStatus", "false");
    net.onConnection = [](const hv::SocketChannelPtr &channel)
    {
        if (channel->isConnected())
        {
            // connected
            BusinessNetWork::getInstance().newNetConnection(channel);
            AppConfig &config = AppConfig::getInstance();
            config.runtimeValue("ConnectionStatus") = "true";
            BusinessViewModel::getInstance().updateStatusbarPermanentText("当前已成功连接: " + channel->peeraddr(), true);
            // if this connect is reconnect, login server automatically
            DEBUG_INFO("config runtime value: authenticated:" + config.runtimeValue("Authenticated"));
            if (config.runtimeValue("Authenticated") == "true")
            {
                // reconnect server
                DEBUG_INFO("reconnect server");
                std::string account = config.runtimeValue("Account");
                std::string shaPassword = config.runtimeValue("Password");

                ThreadPool::getInstance().commit([](std::string account, std::string shaPassword)
                                                 { BusinessNetWork::getInstance().loginTargetServer(account, shaPassword); },
                                                 account, shaPassword);
            }
        }
        else
        {
            BusinessViewModel::getInstance().updateStatusbarPermanentText("与服务器断开连接", false);
            BusinessNetWork::getInstance().deleteNetConnection(channel);
        }
    };

    // when receive message
    net.onMessage = [](const hv::SocketChannelPtr &channel, hv::Buffer *buf)
    {
        BusinessNetWork& net = BusinessNetWork::getInstance();
        std::shared_ptr<Buffer> one = net.getChannelBuffer(channel);
        std::string& buffer = one->buffer;
        int64_t& totalSize = one->totalSize;
        int64_t& addedSize = one->addedSize;

        auto bufSize = buf->size();
        memcpy(buffer.data() + addedSize, buf->data(), bufSize);
        // update added size
        addedSize += bufSize;

        ProtocolConverter converter;
        converter.setProtocolOption(BusinessNetWork::getInstance().getProtocolOption());
        ProtocolBuilder builder;
        builder.setProtocolOption(BusinessNetWork::getInstance().getProtocolOption());

        while(addedSize >= totalSize && addedSize != 0) {
            // complete data received

            // 如果存在一个完整的协议
            if (totalSize > 0) {
                std::shared_ptr<Protocol> protocol = builder.buildFromRawData((void*)buffer.data(), totalSize);
                // 执行这个协议，并从缓冲区中删除
                ThreadPool::getInstance().commit([](std::shared_ptr<Protocol> protocol, const hv::SocketChannelPtr &channel){
                    BusinessMsgHandler::getInstance().handleMsg(protocol, channel);}, protocol, channel);

                std::copy(buffer.begin() + totalSize, buffer.begin() + addedSize, buffer.begin());

                addedSize -= totalSize;
            }
            // 如果缓冲区中还有元素，则读取下一个元素的长度

            if (addedSize >= converter.getHeaderSize()) {
                std::shared_ptr<Protocol> protocol = builder.buildFromRawData((void*)buffer.data(), addedSize);
                // 解析协议的长度
                converter.parseData((unsigned char*)protocol->data());
                // update protocol size
                totalSize = std::get<int64_t>(converter.getHeaderField(5));
            } else {
                // 如果缓冲区中的元素长度不足够读取到协议的长度，则不读
                totalSize = 0;
            }
        }
    };

    net.onWriteComplete = [&](const hv::SocketChannelPtr &channel, hv::Buffer *buf)
    {
        sendLock.unlock();
    };
}

std::shared_ptr<ProtocolOption> BusinessNetWork::getProtocolOption()
{
    return protoOpt;
}
